ESG Framework 1.0 beta 3

...is a FaceSpan template intended to make doing some actions easier.  Right
now it includes:


Bringing "Where is the application?" dialogs under your control, making your
application safer to use and preventing the user from picking apps that won't
work with your application.

Providing an area to initialize and clear out your globals, along with making
it easy for you to prevent your application from trying to work without going
through its initialization routine.

Making localization easier by providing a string lookup mechanism, and
providing a way to use menus without having to remember indices or refer to
their text names.

Loading and saving preference files as script objects, including a way to make
it automatic when your app is launched and quit.

Dialogs that replace the Display Dialog command (bringing the dialog process
under your control.)  FS dialogs are safe to call even if your app is floating
on another app.

Prefab Splash Screen and About dialogs (which you can change to look any way
you want, of course).




Requirements

FaceSpan 3.0.1 or higher.

ESG Framework requires the Finder for some operations to succeed.  If you are
running in an environment where you quit the Finder, some operations will not
work.  Notably, this includes saving and restoring prefs, as well as avoiding
the "Where is the application?" dialog.  We highly recommend you do not quit
the Finder in an ESG Framework-based app.


Using the ESG Framework's Features

Introduction
(Getting there!  No more features, honest!)


The invalid Constant

The invalid constant (constant OthrEdon for use in plugins) is used by
FaceSpan in many places.  Some Framework routines return invalid when an error
has occurred that the routine can't handle in any other way.  The routines
that do this will say so in their descriptions.  Make sure you check the
return value and handle it.  (I wouldn't expect you to see invalid in most
running apps, but you should handle it anyway.)


Resolving tell app calls with the Alias Method

One way to avoid seeing the Where is the Application? dialog is by creating
aliases to the appropriate applications in the folder with your app.  The
aliases must be named the same as the name of the application on the machine
the application originally came from.

To make this work, you have to create a list of records in the SplashScreen
dialog.  You set this list in a property, myAppInfo.  The records you use
(called appRecs) have this format:

{appCode:(creator code of theApp),
 shortName:2,
 fullName:"Name Of App On Your System"
 mustExist:false}

Here's what Netscape's appRec might look like:
{appCode:"MOSS", shortName:2, fullName:"Netscape Communicator",
mustExist:true}

The appCode is the app's creator code.

The shortName is an index into your string table (tblMessages, accessed with
LookupMessage() ) which is the name the framework uses in dialogs.  It should
be the least specific name for the application that will still fit your
application's needs (remembering that newer versions of the app may have been
released after your application).

The fullName is the name as it appears in tell application "fullName" texts on
your machine.  This does not have to match the app's name on the user's
machine, but it will match the name used in the alias file.

If mustExist is true, the app will stop running if it/the user can't find the
file.  If mustExist is false, your app will continue if it isn't found.



Saving and Loading Preferences

The Framework automatically reloads your preferences at startup.  You are
responsible for calling WritePrefs() to save your preferences.  One way to do
so is to put a call to WritePrefs() in the quit handler.

There are also three prefs-related routines you will want to look at:

PrefsNotReadable()

This is called when your app tried to read in a preferences file, but it
failed.  If you want to warn the user or otherwise do something about your
preference file failing, here's the place to do it.  Note that the app will
continue to open (it will init with default preferences).

CopyPrefsToApp()

This is called by ReadPrefs() to take the loaded preferences script object and
put it in your globals or whatever you plan on doing with preferences.  This
routine must successfully initialize whatever preferences structures your
application uses (the application will continue opening even if ReadPrefs
indicates it had a problem).

CopyAppToPrefs()

This is called by WritePrefs() to take the app's current preferences and place
them in a clean preferences script object.


Preference File Name

The name of your preferences file is stored in item 10 of the FrameWork string
table in SplashScreen.




Localizable Menus

  The Framework uses the gMenus global to enable you to easily work with menus
without knowing what their current titles are.  The gMenus global is
initalized from the storage item siMenuInfo, which is one you should alter.

  In siMenuInfo, each menu should have a property which is its index in the
menu bar.  Each menu item is a list of indices which track down the heirarchy.

For example, the menu item File->Page Setup from the Development Environment
would look like:

property mFile : 2

property miFilePageSetup : {mFile, 11}


The sub-menu item Object->Alignment->Align Lefts would look like:

property mObject : 4
property mObjectAlignment :  17

property miObjectAlignmentAlignLefts : {mObject, mAlignment, 1}

As they get longer, you'll definitely want to abbreviate some things. :)

You can also make lists of menu item properties which can be passed to
routines which enable/disable/check/uncheck the entire list for you:

property milExample : {miFilePageSetup, miObjectAlginmentAlignLefts}



There are a number of routines to make it easy to use these routines:

	GetMenuRef(fwMenuReference)

GetMenuRef(fwMenuReference) will return an object reference for the specified
menu or menu item from the menu reference defined in gMenus.  You can use the
reference to change properties of the menu item (checked/enabled/menu
text/etc.)

If the menu you ask for doesn't exist, GetMenuRef returns invalid.

	SameMenu(menuReference, fwMenuReference)

SameMenu will return true if the two references are to the same menu, false if
they're not or one of them doesn't exist.  You'll use this in your on chosen
handler(s).

menuReference is a FaceSpan menu object reference (menu item x of menu y),
while fwMenuReference is one of the properties in gMenus.


	EnableMenu(menuList)
	DisableMenu(menuList)
	CheckMenu(menuList)
	UncheckMenu(menuList)
	
These four routines take either a Framework menu reference or a list of
Framework menu references and sets their properties as indicated.

	SetEnabled(menuList, newVal)
	SetChecked(menuList, newVal)
	
These let you control the new value (for example, if it's a variable).  Again,
the routines take a list of references or just one and the new value for the
appropriate property.

	SetMenuName(fwMenuReference, newName)
	
This sets the menu/menu item's name.


Don't forget that your constants are all stored in gMenus, so you need to
refer to them like this:

SetChecked(milExample of gMenus, true)


While this might seem to be a pain to set up, it should be fairly easy to
write a script that will look at a menu heirarchy from in FaceSpan and create
the properties for you.  Look for it in the next version of Framework.





Routines provided for you

(there are others, and many routines are documented in the source itself)

  LookupMessage(messageIndex)
  LookupFWMessage(messageIndex)
  
These two commands lookup messages in your message list or the framework's
message list (respectively).  By using these wherever you would have used a
string literal (as in "This is a String Literal"), it's very easy to localize
your application.


  DoDialog(dlgRec)
  DoErrorDialog(dlgRec)

Call this when you would have used display dialog to use FaceSpan dialogs
instead.  This prevents any possible problems using modals when your app isn't
frontmost (FaceSpan dialogs are always safe; OSAX-dialogs may not be.)


  DoMessageDialog(messageText)
  
This does a DoDialog() with just a message and an OK button, common when
reporting errors or other status information.


  IsOK(someText)
  IsCancel(someText)
  IsQuit(someText)
  IsYes(someText)
  IsNo(someText)
  
These are useful to see which button was pressed in your DoDialog().  These
use localized versions of OK/Cancel/Quit/etc. for comparison.  Since DoDialogs
default to OK and Cancel for the buttons, your DoDialog calls might wind up
looking like this:

  set theResult to DoDialog({dlgText:LookupMessage(3)})
  if IsOK(dlgButton of theResult)
    -- blah blah
  end if
  

  GetOK()
  GetCancel()
  GetQuit()
  GetYes()
  GetNo()
  
These return the localized OK/Cancel/Quit/etc. text, useful for shortening
your DoDialog lines.


  ReadPrefs()
  WritePrefs()

These two routines make it easy for you to read and write your application's
preferences to files in the preferences folder.  The default Init() routine
calls ReadPrefs().  All you have to do is make sure WritePrefs() is called
when you want to save your preferences.


  InitApp()

InitApp() should be called by every planned or possible accidental entry point
in your application.  This includes run, open, reopen, chosen, and any events
your app expects to receive from other applications/users writing scripts.  If
InitApp returns false, the app could not initialize properly and should quit. 
If InitApp returns true, either the app was already initialized or it
successfully initialized.


  SplashScreen:Init()

This is where all the initialization for your application should be placed. 
This is where the framework looks for the applications you want to tell to and
handles making sure FaceSpan can find them.  This is called once and only once
per run by InitApp(), unless someone is deliberately trying to hose your
application.



  AppFound()
  
AppFound lets you ask if an application is available for you to use.  You can
ask by the creator code or name of the app.





Things to look for when starting a new app

Change the properties of the Project Script to fit your application.

Look everywhere for the word "Framework" and substitute in your app's name. 
It would be much nicer if Framework came with a script that did this for you.

Search the scripts for "FIX THIS".  These words are found in various places
where you will probably have to alter the template to make your app work the
way you want it to.

Edit the text in the About Dialog.



Things to do when you are ready to distribute your application

If you're going to distribute a run-only application that uses preferences,
you should also copy your siBlankPrefs script into the Script Editor, save as
a run-only script object, then load script that object into the siBlankPrefs
storage item.  That way people can't read your preferences files either.  This
script should work when run from the Message window:

set contents of storage item "siBlankPrefs" to load script file "yourFile"

Once you do this, you will not be able to open the storage item unless you
clear it out (set contents of storage item "siBlankPrefs" to "", or something
similar).

kDebug is a property in the Project Script that tells the framework whether it
should report errors in framework-related things (like the DoDialog command)
or if it should just hide these problems.  It also tells the framework to do
some things automatically (like closing and hiding the disclosure triangle in
the Splash Screen).  Set kDebug to false when shipping.  



Things you shouldn't do

You cannot tell to any outside application (except the Finder) before
InitApp() successfully completes.  You cannot put any tell statements to
outside applications in any script loaded before the aliases are set up in
SplashScreen's Init() handler.  This should only include the Project Script
and SplashScreen.

In general, you should not change the contents of table "tblFWMessages",
except to fix app names or localize the text.  These messages are used by the
framework.  Generally, you shouldn't change the contents of storage item
"siFrameworkRoutines", but advanced users may want to replace routines in it
with different methods (preferences, for example).  The names of any of the
storage items that come with Framework need to be left alone as well.  Lastly,
you shouldn't change the standard four icons that come with without updating
any places in the Framework they appear with a decent replacement.

The ESG Framework was meant to run as a complete application.  There is no
good way to prevent the "Where is the Facespan Extension?" dialog in a mini
app, and if your app is in the background when it appears it will crash. 
(This is true of any mini app, not just ones made with this framework.)

When you are working in the Development Environment, make sure you close all
of your windows before Running your app.  If the SplashScreen is open it can
mess up your app.  (This is true of any FaceSpan app, not just ones made with
this framework.)



Future Ideas and Other Notes

From experiences in creating AmSt 1.3, here's some info about what worked well
and what didn't in AmSt 1.3

Here's what seems to work well:

Menus
Localizable Messages
Select All mechanism
The Alias Method (for preventing the Where Is...? dialog)


Here's what hasn't been working so well:

Init() is slow.  I suspect this is due to using the Finder to create and
verify aliases.  Unless I want to commit to an OSAX that allows faster
creation and verification of alias files (unlikely), this won't change.  It
does work properly, however.

Redirects and utility functions in the Project Script.  While it's very nice
to have these there (because it reduces typing for the popular routines) it
lowers encapsulation and forces globals into the Project Script.  Also, these
are a security risk, since they're all potential entry points into your app.

Preferences as script files.  They work, but to make them bulletproof you need
to either trap every access to preferences with something that resets them to
defaults or when loading preferences scan the data and verify that the various
items are what you expect.  Loading prefs via an OSAX that can write AS
structures may be better, but it is probably even more work than verification
on load.  (If nothing else, you have to write a load and a store.  In the
current scheme you assume that all data is valid when you store.)

Named globals in scripts and script apps are easy to find (since the base
source is public).  If someone wants to mess with your app they can do so
fairly easily.  This could possibly be handled in a "create final app" script
by scrambling the names of the globals?

The various Debug functions don't work as well as I'd hoped, though it helped
debug some things in the Framework.  These might all disappear by 1.0.



One of the goals of the Framework is for you to be able to move to a new
version of the Framework when it is released without having to do a huge pile
of work.  This requires separating a number of pre-written partial functions
(preferences, for example) into pre-written and user-changable sections.  This
requires enhancements to various parts of the Framework that will make it
require less altering to fit the application and/or moving things that need to
be altered out of the Framework routines (this would free up space in the
Project Script, and look nicer to less-experienced FaceSpan creators).  This
implies more encapsulation of the Framework routines in siFWRoutines and the
Framework section of the Project Script.  (The real goal is for you to only
have to copy your windows, artwork, forms, and non-framework storage items
over, cut and paste the text from the top of the Project Script to the top of
the new Project Script, and fix the menus.)  It'd be even nicer if some kind
of script could do this for you...

Scripts that automate many of the common changes/additions/whatnot that need
to be made to the Framework.  One would be a script that will take your menu
structure and build a siMenuInfo script for you.  This should be somewhat
simple now.

Clean up SplashScreen's script.

Perhaps create a Shutdown() routine in SplashScreen, and have quit call that
by default?  The default Shutdown could bring SplashScreen back to the front
with a message "shutting down", call WritePrefs, close all open windows, then
return true if everything worked, or false if not.


Either/or tags on creating aliases.  (So your app requires Netscape or
Internet Explorer or both, but needs one.)

A Framework-based Plugin loading system.

Need to add more error trapping in critical areas.  (Most notably
preferences.)

ReadPrefs should take a file, and if that file is not 'invalid' it tries to
read preferences from that file.  (The easy way to handle someone opening a
preferences file.)

Multiple-user operation.  The Alias Method is not multiple-user compatible at
this time, because it needs to write to its own folder and applications are
not guaranteed to be able to write to their own folder in the multiple-user
setup.  Even before OS9, At Ease would prevent this from working, but if all
you're concerned about is OS9, you can just disable alias creation and use the
new OS9 methods to make your scripts compile and run safely.



I need to look at the function structure used in making aliases.  It's already
become pretty messy.

A decent way to check for Scripting Additions (probably by name, as the
easiest way to check to see if it's actually responding is to do something
harmless that requires the OSAX.)

Finish the Generic Select All handler, including checking for appropriate
selection abilities.


Additions ideas:

An Outline Listbox made through creative scripting and the List with Metas
form.  If it ever exists, it will be clunky and slow.

A prototype "Wizard/Assistant" dialog, made by faking it with a tab panel.

A Status Window, with update functions and logging.

...and I'm always looking for more ideas!



Version History

Changed in 1.0b3

Ran into a problem with setting the titled property of windows at runtime. 
This has been disabled in this version of the Framework (the DoDialog()
windows have had their titled properties set to false).  I'm not sure if this
means two different sets of windows (to have titled and non-titled) or picking
a generic title if one is not provided (the app's name might be a good
choice).

Added siMenuInfo, gMenus, and the localizable menu setup.

There is no more QuitApp(), you just add the code directly to the quit
handler.  Quit, like chosen and run, is sufficiently unlikely to change to
make getting rid of the abstraction feasible.  Unfortunately, this makes it
harder to determine some data (say, if the app successfully initialized before
quit was called.)

Added a SetStatus() routine to Set and draw the Status label in SplashScreen.

Framework now comes with another project: "Framework Additions".  This will
contain useful windows/dialogs that you can just cut and paste into your
Framework project.  The first one is an RGB Color Picker.

Added VerifyApps() to the Framework Routines.  Call this to have the Framework
reverify the app aliases.  (This isn't really that useful, because usually by
the time you'd want to call it it's too late, but if you want it you've got
it.)

You can now specify that an application is not required.  The Framework will
look for it but will continue on if it isn't there.

Added a routine (AppFound) to look up which apps were found during the
resolution process.  You can use either the creator code or the name on the
development machine to look it up.

The Framework now asks the Finder for an application file of the appropriate
creator code, and uses that file if it is found.  You can easily disable both
the confirmation dialog and the entire searching with the Finder by changing
some constants.

Changed how we check for running in the Development Environment.

Added DoMessageDialog(), described above.

Added Select All to the Edit menu, and added a default way to handle Select
All.  Read the comments in the project script for more info.

Added a generic error trap in the Project Script for show balloon (then
disabled it, see below).  If you didn't know, it's possible to get an error
back from show balloon if you have a script attached to an object that handles
show balloon and the user moves the mouse really fast.  This error trap
prevents returning any error from show balloon.  Most show balloon handlers
exist to change the text of the show balloon (say, when a window item is
disabled).

Disabled the show balloon idea, because if you hover over a window item that
doesn't have balloon text assigned to it, the cursor switches between the
beach ball and the arrow.  Doh!  (You can see this in AmSt's shipped code.)



Changed in 1.0b2

Moved the short name part of an appRec into the string tables, so that it can
be localized.

The Alias creation routines will now make new aliases if the name no longer
matches.  (It ignores aliases that are the right creator code but the name
doesn't match...)

Added scanning for applications using the Finder.

Added automatic hiding or showing of the text lists in SplashScreen based on
kDebug.

Moved much of ReadPrefs() and WritePrefs() into siFrameworkRoutines.  They now
call a few routines in the Project Script that the app designer might modify. 
The overall effect is a smaller Project Script. 

Fixed a bug in and the name of "About Framework" (it was calling a routine
that no longer exists.)

Fixed a naming error in SplashScreen: the caution icon should be named
icnYourIcon instead of icnESGLabs.

Added a bunch of quick accessor functions for the most common Framework
strings: OK, Cancel, Quit, Yes, and No.

Fixed the positioning of the string tables (you could see the top of them in
the Alias Method demo app).


Legal Info

The ESG Framework is provided as freeware for FaceSpan developers to use as
they see fit.  I'd appreciate it if (somewhere in your documentation) you
mentioned your app is based on the ESG Framework and include a pointer to the
website so others can get a copy if they might find it useful.


Hopefully you will find this useful, and I'd appreciate any feedback you have.

Michael Miller
ESG Labs
http://www.esglabs.com/
techsupp@esglabs.com
June 1, 2000
